Skip to content

feat(hive): HiveSignMessage — Keychain signBuffer, unblocks dApp login#306

Closed
BitHighlander wants to merge 4 commits into
fix/hive-sign-hardeningfrom
feat/hive-sign-message
Closed

feat(hive): HiveSignMessage — Keychain signBuffer, unblocks dApp login#306
BitHighlander wants to merge 4 commits into
fix/hive-sign-hardeningfrom
feat/hive-sign-message

Conversation

@BitHighlander

Copy link
Copy Markdown
Owner

Implements P3 from the Hive full-support audit: the dApp login primitive. SkateHive and every Aioha/Keychain-SDK dApp authenticates via requestSignBuffer — a message signature with the posting key that the dApp verifies by pubkey recovery against the account's on-chain authority. It cannot be shimmed host-side. Notably: Hive Keychain itself throws sign_buffer_ledger_error for Ledger accounts, so no hardware wallet can do Hive dApp login today.

Stacked on #305 (fix/hive-sign-hardening) — uses its SLIP-48 enforcement conventions; GitHub will retarget this to develop when #305 merges.

Contract (verified against Keychain source, hive-js Signature.signBuffer)

  • Digest = SHA256(raw message bytes), once — no chain_id prepend (unlike transactions), no Bitcoin/Solana-style prefix. A wrong prefix silently breaks all dApp verification (the Solana-prefix lesson).
  • Output = 65-byte compact recoverable sig: sig[0] = 27 + recid + 4, then r‖s; low-S via the existing hive_is_canonic callback.
  • Response also carries the 33-byte compressed pubkey so the host builds Keychain's publicKey field without a second device round-trip.

Message IDs: 1614/1615 (not 1610/1611)

The sign-message handoff proposed 1610/1611, but NEAR holds 1610-1613 on device-protocol master (PR #32, merged). Registry note added in messages.proto. Follow-on Hive primitives (P4 HiveSignOperations, P5 memo crypto) must start at 1616.

Security fences

  • Full SLIP-48 path shape enforced (m/48'/13'/role'/account'/0', 5 hardened components) exactly like the tx handlers; any of the four roles may sign (login uses posting').
  • Chain-id-prefix collision refused: a tx digest is SHA256(chain_id || tx), so a crafted "message" beginning with the mainnet chain-id bytes would hash to a broadcastable transaction's digest. No legitimate challenge starts with those 32 bytes; the handler rejects them. (Keychain itself does not guard this.)
  • 1024-byte cap in both proto options and handler code (the memo F4 lesson — proto and code can never disagree).
  • Printable text displayed as-is on-device; other payloads as a 32-byte hex preview + length.

Pins

  • deps/device-protocol9e46aeb (feat/hive-sign-message off the current pin 33521a8, only the two new messages — no unrelated proto drift)
  • deps/python-keepkey591dba8 (feat/hive-sign-message off fix(hive): enforce SLIP-48 path in SignTx + specific memo-length error #305's pin 35555d7): hive.sign_message(), wire-id mapping, pb2 regen with the pinned protoc 3.5.1 (docker v8)

Verification

  • pyk on UDP emulator built from this branch: 18/18 hive tests — the 11 existing (fix(hive): enforce SLIP-48 path in SignTx + specific memo-length error #305 suite) plus 7 new: posting login recovery (the exact dApp check: recover from SHA256(message), assert ∈ {response public_key, independently-derived HiveGetPublicKey}), all-four-roles distinctness, non-printable buffer, 1024 boundary, 1025 reject, path fence (BIP-44 / 3054' / unassigned role / short path), chain-id-prefix reject.
  • firmware-unit: 246/247 — the one failure is Storage.StorageRoundTrip, the documented macOS-ARM64 golden-vector platform artifact (Linux CI is the source of truth for it).
  • Docker (linux/amd64, kktech/firmware:v15) make kkfirmware clean; generated nanopb has zero pb_callback_t (options caps complete).
  • On-device OLED proof: pending CI screenshot run / rc flash (Gate 3).

Release scope

7.15.0 is still untagged; the handoff recommends shipping this in the 7.15.0 rc train so Hive launches dApp-login-capable instead of transfer-only. Version gates in the pyk tests are set to 7.15.0 accordingly — flip to 7.15.1 if the decision goes the other way.

…ks dApp login

Digest is SHA256(raw message bytes) ONLY — no chain_id prepend, no
message prefix (the hive-js Signature.signBuffer contract every Hive
dApp verifies against). Output: 65-byte compact recoverable sig
(27+recid+4) + the 33-byte compressed pubkey so the host can build
Keychain's publicKey response field without a second round-trip.

- Full SLIP-48 path shape enforced like the tx handlers; any of the
  four roles may sign (dApp login uses posting')
- Messages beginning with the mainnet chain id are refused: such a
  buffer would hash to a broadcastable TRANSACTION digest
  (tx digest = SHA256(chain_id || tx))
- 1024-byte cap in proto AND handler (memo-length lesson)
- Printable text shown as-is, other payloads as a hex preview
- hive_sign_raw_digest factored out of hive_sign_digest (shared
  header/recid logic, no duplication)
- IDs 1610-1613 skipped: NEAR holds them on device-protocol master
- pyk: sign_message + 7 signBuffer tests (recovery, roles, boundary,
  oversize, path fence, chain-id collision)
…ffer

Finding 1: printable text over 128 bytes now routes through the hex
preview + byte count instead of confirm()'s silently-truncating body
buffer (BODY_CHAR_MAX) — a long message could otherwise show a benign
prefix while the device signs hidden trailing content. Same budget as
SolanaSignMessage.

Finding 2: owner' removed from the accepted signBuffer roles. Keychain's
requestSignBuffer surface is Posting | Active | Memo only; no consumer
offers owner', and the cold owner key must not be normalized into dApp
flows.

Also: comment now says printable ASCII (0x20-0x7e), not UTF-8.
Pins: device-protocol a793934 (role doc), python-keepkey ed8cbdf
(19/19 on emulator incl. new >128B printable boundary + owner' reject).
@BitHighlander

Copy link
Copy Markdown
Owner Author

Both findings addressed in ddade55:

  • Finding 1: printable display now gated at 128 bytes (Solana's budget); longer printable text routes through the hex-preview + byte-count confirm, so confirm()'s BODY_CHAR_MAX can never silently truncate what the user approves. New pyk test: 300-byte printable message signs via the preview branch.
  • Finding 2: owner' dropped from the accepted roles — the switch now matches Keychain's requestSignBuffer surface (Posting | Active | Memo). owner' moved to the reject-fence test.
  • Comment fixed: printable ASCII (0x20–0x7e), not UTF-8. device-protocol doc comment updated to match (a793934).

Re-verified on a rebuilt UDP emulator: 19/19 hive tests. Testnet chain-id fence left out per the review's no-change-required note — mainnet is the broadcastable-funds case.

@BitHighlander BitHighlander deleted the branch fix/hive-sign-hardening July 15, 2026 19:33
@BitHighlander BitHighlander deleted the feat/hive-sign-message branch July 15, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant